package View;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JPanel;

import objects.Exon;
import objects.Gene;
import objects.Subset;
import objects.Transcript;
import objects.Treatment;
import objects.alignments.Alignment;

import Control.Controller;
import Control.Zoomable;

@SuppressWarnings("serial")
public class GeneViewport extends JPanel 
{

	private static GeneViewport instance = null;
	private Zoomable z;

	protected GeneViewport() {
		this.setOpaque(true);
		
		z = Zoomable.getInstance();
		z.registerComponent(this);
	}

	public static GeneViewport getInstance() {
		if (instance == null) {
			instance = new GeneViewport();
		}
		return instance;
	}

	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);		

		Gene gene = Controller.getInstance().getCurrentGene();
		if (gene == null) {
			return;
		}
		
		int totalDistance = Controller.getInstance().getCalculatedDistance();
		if (!z.isSet())
			z.autoZoom(totalDistance);
		totalDistance = z.calcDist(totalDistance);	
		
		// Update the size of the JPanel if the totalDistance changes..
		if (Math.abs(totalDistance  - this.getWidth()) > 50)
		{
			this.setPreferredSize(new Dimension(totalDistance, getHeight()));
			this.validate();
//			System.out.println ("Dimensions have changed");
//			System.out.println ("New Dimensions: " + getWidth() + " v. " + getHeight());
//			System.out.println (" Total Distance: " + totalDistance);
		}
		
		// Draw the Rectangle behind the Gene
		g.setColor(new Color(230, 230, 230));
		g.fillRect(z.calcDist(Controller.getInstance().getOffset()), 0, z.calcDist(gene.getValidDistance()), getHeight());

		// Grab the most Active Treatment
		// TODO: update it so that all the treatments are displayed together..
		Treatment active = Controller.getInstance().getLastTreatment();
		int yoffset = 0;

		if (active != null) {
			Font h2 = new Font("Georgia", Font.PLAIN, 18);
			Font normal = g.getFont();
			Graphics glassGraphic = this.getRootPane().getGlassPane().getGraphics();
			glassGraphic.setFont(h2);
			glassGraphic.setColor(new Color(80, 80, 80));
			glassGraphic.drawString("Treatment " + active.getTreatmentID() + ". Note: 1 Bar = " + z.getPositions() + " Position(s)", 8, g.getFontMetrics().getHeight() + 2);
			yoffset = glassGraphic.getFontMetrics().getHeight() + 8;
			glassGraphic.setFont(normal);
		}
		
		drawGeneModel(g, gene, z.calcDist(Controller.getInstance().getOffset()), getHeight() - 40, 40);

		if (Controller.getInstance().isCollapsed()) {
			drawCollapsedSubsets(gene, 0, yoffset, getWidth(), getHeight() - 40
					- yoffset);
		} else {
			drawSubsets(0, yoffset, getWidth(), getHeight() - 40 - yoffset);
		}
	}

	private void drawCollapsedSubsets(Gene g, int x, int y, int width,
			int height) {
		// TODO: Calculate max size
		Gene gene = Controller.getInstance().getCurrentGene();

		// Draw the treatments in the background lightly
		List<Integer> activeTreatments = Controller.getInstance()
				.getActiveTreatments();
		for (int treatment : activeTreatments) {
			Treatment t = gene.getTreatmentByID(treatment);
			if (t != null) {
				drawCollapsedSubsetsByTreatment(t, x, y, width, height, 40);
			}
		}

		// Get the currently active treatment
		Treatment active = Controller.getInstance().getLastTreatment();
		if (active != null) {
			drawCollapsedSubsetsByTreatment(active, x, y, width, height, 255);
		}
	}

	private void drawCollapsedSubsetsByTreatment(Treatment t, int x, int y,
			int width, int height, int opaqueness) {
		if (t == null) {
			return;
		}

		List<Subset> subsetList = t.getAllSubsets();

		List<Alignment> allSubsets = new ArrayList<Alignment>();
		Subset subset0 = null;

		for (Subset subset : subsetList) {
			if (subset.getSubsetID() != 0) {
				allSubsets.addAll(new ArrayList<Alignment>(subset
						.getAllAlignments().values()));
			} else {
				subset0 = subset;
			}
		}
		Collections.sort(allSubsets);

		Subset collapsed = new Subset(-1, allSubsets, t);

		int subsetHeight = (int) Math.round(Math
				.floor(((height - 10) * 1.0) / 2.0));
		drawSubset(collapsed, 0, 0, width, subsetHeight, opaqueness);
		drawSubset(subset0, 0, height - subsetHeight, width, subsetHeight,
				opaqueness);
	}

	private void drawSubsets(int x, int y, int width, int height) {
		Gene gene = Controller.getInstance().getCurrentGene();

		// Draw the treatments in the background lightly
		List<Integer> activeTreatments = Controller.getInstance()
				.getActiveTreatments();
		for (int treatment : activeTreatments) {
			Treatment t = gene.getTreatmentByID(treatment);
			if (t != null) {
				drawSubsetsByTreatment(t, x, y, width, height, 40);
			}
		}

		// Get the currently active treatment
		Treatment active = Controller.getInstance().getLastTreatment();
		if (active != null) {
			drawSubsetsByTreatment(active, x, y, width, height, 255);
		}
	}

	private void drawSubsetsByTreatment(Treatment t, int x, int y, int width,
			int height, int opaqueness) {
		if (t == null) {
			return;
		}

		// Get the subset list
		List<Subset> subsetList = t.getAllSubsets();

		// Calculate the subset Height
		int subsetHeight = (int) Math.round(Math.floor(((height - 10) * 1.0)
				/ subsetList.size()));

		// Draw the Subsets
		for (Subset s : subsetList) {
			int sID = s.getSubsetID();
			int subsetStart = (sID == 0) ? (height - subsetHeight) : (sID - 1)
					* subsetHeight;
			subsetStart += y;
			drawSubset(s, x, subsetStart, width, subsetHeight, opaqueness);
		}
	}

	private void drawSubset(Subset s, int x, int y, int width, int height, int opaqueness) {
		// Choose the Bar Width
		int barWidth = (int) z.getPositions();
		
		// Calculate how many positions per bar
		Object[] obj = calculateMax(s, barWidth);
		if (obj == null || obj.length != 3) {
			return;
		}
		int maxCount = (Integer) obj[0];
		int total = (Integer) obj[1];

		@SuppressWarnings("unchecked")
		HashMap<Integer, Integer> counts = (HashMap<Integer, Integer>) obj[2];

		Graphics2D g2d = (Graphics2D) this.getGraphics();

		g2d.setColor(new Color(167, 219, 216));
		g2d.drawLine(x, y + height - 16, x + width, y + height - 16);

		FontMetrics fontmetrics = g2d.getFontMetrics();

		if (opaqueness == 255) {
			Color textColor = new Color(150, 150, 150);

			this.getRootPane().getGlassPane().getGraphics().setColor(textColor);

			if (s.getSubsetID() == -1) {
				this.getRootPane().getGlassPane().getGraphics().drawString("Collapsed Subset", x + 8, y + height);
			} else {
				this.getRootPane().getGlassPane().getGraphics().drawString("Subset " + s.getSubsetID(), x + 8, y + height);
			}
			String totalString = Integer.toString(total);
			g2d.drawString(totalString, x + width - 8
					- g2d.getFontMetrics().stringWidth(totalString), y + height);
		}

		drawBars(s.getTreatmentParent().getColor(), counts, maxCount, y,
				barWidth, height - (fontmetrics.getHeight() + 2), opaqueness);
	}

	private void drawBars(Color color, HashMap<Integer, Integer> counts,
			int maxCount, int y, int barWidth, int height, int opaqueness) {
		Graphics2D g2d = (Graphics2D) this.getGraphics();

		for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {
			if (entry.getValue() > 0) {
				int barHeight = (int) Math.round(entry.getValue() * 1.0
						/ maxCount * (height * .8));
				int geneOffset = Controller.getInstance().getOffset();
				g2d.setColor(new Color(color.getRed(), color.getGreen(), color
						.getBlue(), opaqueness));
				g2d.fillRect(z.calcDist(geneOffset + entry.getKey()), y
						+ (height - barHeight - 1), barWidth - 1, barHeight);
			}
		}
	}

	private Object[] calculateMax(Subset s, int barWidth) {
		Gene g = s.getTreatmentParent().getGeneParent();
		int offset = Controller.getInstance().getOffset();

		double positionsPerBar = barWidth * z.getPositions();

		int min = -1 * offset;
		int max = g.getValidDistance() + offset;

		HashMap<Integer, Alignment> alignments = s.getAllAlignments();

		int maxCount = 0;
		int total = 0;
		// Go Backwards and calculate Max
		HashMap<Integer, Integer> counts = new HashMap<Integer, Integer>();
		for (int i = -1; i > min; i = i
				- ((int) Math.floor(positionsPerBar) + 1)) {
			int count = 0;
			for (int num = i; num > i - positionsPerBar; num--) {
				if (alignments.containsKey(num)) {
					Alignment a = alignments.get(num);
					count += a.getCount();
				}
			}
			counts.put(i, count);
			if (maxCount < count) {
				maxCount = count;
			}
			total += count;
		}

		// Go Forward and calculate Max
		for (int i = 0; i < max; i += (int) Math.ceil(positionsPerBar)) {
			int count = 0;
			for (int num = i; num < i + positionsPerBar; num++) {
				if (alignments.containsKey(num)) {
					Alignment a = alignments.get(num);
					count += a.getCount();
				}
			}
			counts.put(i, count);
			if (maxCount < count) {
				maxCount = count;
			}
			total += count;
		}

		Object[] o = { maxCount, total, counts };
		return o;
	}

	/**
	 * 
	 * @param g
	 * @param gene
	 * @param x
	 *            The x offset to start at to draw the Gene
	 * @param y
	 *            The y offset to start at to draw the Gene
	 * @param maxHeight
	 */
	private void drawGeneModel(Graphics g, Gene gene, int x, int y,
			int maxHeight) {

		int transcriptSize = gene.getTranscripts().size(); // The number of
															// transcripts
		int middle = (int) Math.round(maxHeight * 1.0 / 2) - 5; // Find the
																// middle of the
																// area able to
																// draw in.
		int border = 20;
		int above = transcriptSize / 2;
		int startY = (transcriptSize % 2 == 0) ? middle - above * border / 2
				: middle - above * border;

		List<Transcript> transcripts = gene.getTranscripts();
		int index = 0;
		for (Transcript t : transcripts) {
			List<Exon> exons = t.getAllExons();
			for (Exon exon : exons) {
				int exonOffset = exon.getBounds().start
						- gene.getValidPosition().start;
				drawExon(g, exon, x + z.calcDist(exonOffset), y + startY
						+ border * index, 10);
			}
			index++;
		}
	}

	private void drawExon(Graphics g, Exon e, int x, int y, int barHeight) {
		Graphics2D g2d = (Graphics2D) g;

		g2d.setColor(new Color(100, 144, 138));
		Rectangle2D.Double geneRect = new Rectangle2D.Double(x, y, z.calcDist(e
				.getBounds().getDistance()), barHeight);
		g2d.fill(geneRect);
		g2d.setColor(new Color(65, 93, 89));
		g2d.draw(geneRect);
		if (e.hasSharedLeft()) {
			g2d.fill(new Rectangle2D.Double(x + 1, y, 2, barHeight));
		}
		if (e.hasSharedRight()) {
			g2d.fill(new Rectangle2D.Double(x
					+ z.calcDist(e.getBounds().getDistance()) - 2, y, 2,
					barHeight));
		}
	}

	/**
	 * Draws the Gene Model for the current gene. Starts the Gene at (x,y) with
	 * given bar height.
	 */
	private void drawGene(Graphics g, Gene gene, int x, int y, int barHeight) {
		Graphics2D g2d = (Graphics2D) g;

		// Draw the current gene bounds.
		g2d.setColor(new Color(78, 205, 196));
		Rectangle2D.Double geneRect = new Rectangle2D.Double(x, y,
				z.calcDist(gene.getValidDistance()), barHeight);
		g2d.fill(geneRect);
		g2d.setColor(new Color(48, 126, 120));
		g2d.draw(geneRect);
	}
}
